/*
 * ﻿Copyright (C) 2011-2012 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.aspectjutil.eventmanager;

import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;

import junit.framework.Assert;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.LoggerFactory;

public class EventHandlerTestWithEventRecipientGrandChild extends EventHandlerTestTemplate {
	static {
		logger = LoggerFactory.getLogger( EventHandlerTestWithEventRecipientGrandChild.class);
	}
	
	// Tests for @EventListener method inheritance ------------------------------------------------
	@Test
	public void testByTriggeringCalculationEvent3() throws Throwable {
		// Instantiate EventRecipientGrandChild object --------------------------------------------
		Object eventRecipientGrandChildObj 
		= EventManagerTestUtils.eventRecipientGrandChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		
		// Execute EventProducer.calculate method -------------------------------------------------
		Object eventProducerObj = EventManagerTestUtils.eventProducerFactory( childDelegationClassLoader);
		Method calculateMethod 
		= EventManagerTestUtils.getEventProducerCalculateMethod( eventProducerObj.getClass());
		Integer calculationResult = (Integer)calculateMethod.invoke( eventProducerObj, 4, 8);
		// ----------------------------------------------------------------------------------------
		
		// Verifications --------------------------------------------------------------------------
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				3, 
				eventTraceList.size()
				);
			/* Execution order between EventRecipient.staticListenerOfCalculationCompletion and 
			 * EventRecipientChild.staticListenerOfCalculationCompletion is uncertain.
			 */
			boolean eventRecipientFlag = false;
			boolean eventRecipientChildFlag = false;
			for( int eventTraceListIndex = 0; eventTraceListIndex < 2; eventTraceListIndex++) {
				String executedMethodStr = eventTraceList.get( eventTraceListIndex);
				if ( executedMethodStr.contains(
						EventManagerTestUtils.EventRecipientClassName 
						+ ".staticListenerOfCalculationCompletion")
						) 
				{
					eventRecipientFlag = true;
				}
				else if ( executedMethodStr.contains(
						EventManagerTestUtils.EventRecipientChildClassName 
						+ ".staticListenerOfCalculationCompletion")
						)
				{
					eventRecipientChildFlag = true;
				}
			} // for
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), eventRecipientFlag);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), eventRecipientChildFlag);
			
			Assert.assertTrue(
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( 2)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName 
							+ ".staticListenerOfBothEvents"
							) 
					);
			
			for( int eventTraceIndex = 0; eventTraceIndex < eventTraceList.size(); eventTraceIndex++) {
				Assert.assertEquals( 
						EventManagerTestUtils.getCalculationEventClass( childDelegationClassLoader),
						triggeredEventTraceList.get( eventTraceIndex)
						);
				Class<?> eventerInfoClass 
				= EventManagerTestUtils.getEventerInfoClass( childDelegationClassLoader);
				Object eventerInfo = eventerInfoClass.cast( eventerTraceList.get( eventTraceIndex));
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerObjOfEventerInfo( eventerInfo),
							eventProducerObj
							);
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerMethodOfEventerInfo( eventerInfo).getName(),
							"calculate"
							);
				Assert.assertEquals( 
						(Integer)eventResultTraceList.get( eventTraceIndex), 
						calculationResult
						);
			} // for
			
		// ----------------------------------------------------------------------------------------
	}
	// --------------------------------------------------------------------------------------------
	
	// Test for field event -----------------------------------------------------------------------
	@Test
	public void testFieldEventFromEventProducer() throws Throwable {
		// Instantiate EventRecipient object ------------------------------------------------------
		Object eventRecipientObj 
		= EventManagerTestUtils.eventRecipientFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientChild object -------------------------------------------------
		Object eventRecipientChildObj 
		= EventManagerTestUtils.eventRecipientChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientGrandChild object --------------------------------------------
		Object eventRecipientGrandChildObj 
		= EventManagerTestUtils.eventRecipientGrandChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		
		// Execute EventProducer.setPrice method -------------------------------------------------
		Object eventProducerObj = EventManagerTestUtils.eventProducerFactory( childDelegationClassLoader);
		Method setPriceMethod 
		= EventManagerTestUtils.getEventProducerSetPriceMethod( eventProducerObj.getClass());
		int priceValue = 88;
		setPriceMethod.invoke( eventProducerObj, priceValue);
		// ----------------------------------------------------------------------------------------
		
		// Verifications --------------------------------------------------------------------------
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				4, 
				eventTraceList.size()
				);
			int eventTraceListIndex = 0;
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName 
							+ ".staticListenerOfBothEvents"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".onPriceChange"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".staticListenerOfPriceChange"
							) 
					);
			Assert.assertTrue(
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName 
							+ ".onPriceChange"
							) 
					);
			
			for( int eventTraceIndex = 0; eventTraceIndex < eventTraceList.size(); eventTraceIndex++) {
				Assert.assertEquals( 
						EventManagerTestUtils.getPriceChangeClass( childDelegationClassLoader),
						triggeredEventTraceList.get( eventTraceIndex)
						);
				Class<?> eventerInfoClass 
				= EventManagerTestUtils.getEventerInfoClass( childDelegationClassLoader);
				Object eventerInfo = eventerInfoClass.cast( eventerTraceList.get( eventTraceIndex));
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerObjOfEventerInfo( eventerInfo),
							eventProducerObj
							);
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerFieldOfEventerInfo( eventerInfo).getName(),
							"price"
							);
				Assert.assertEquals( 
						(Integer)eventResultTraceList.get( eventTraceIndex), 
						Integer.valueOf( priceValue)
						);
			} // for
			
		// ----------------------------------------------------------------------------------------
	}
	// --------------------------------------------------------------------------------------------
	
	// Test for field event from inherited field --------------------------------------------------
	@Test
	public void testFieldEventFromChildEventProducer1() throws Throwable {
		// Instantiate EventRecipient object ------------------------------------------------------
		Object eventRecipientObj 
		= EventManagerTestUtils.eventRecipientFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientChild object -------------------------------------------------
		Object eventRecipientChildObj 
		= EventManagerTestUtils.eventRecipientChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientGrandChild object --------------------------------------------
		Object eventRecipientGrandChildObj 
		= EventManagerTestUtils.eventRecipientGrandChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		
		// Execute EventProducer.setPrice method -------------------------------------------------
		Object childEventProducer1Obj 
		= EventManagerTestUtils.childEventProducer1Factory( childDelegationClassLoader);
		Method setPriceMethod 
		= EventManagerTestUtils.getEventProducerSetPriceMethod( 
				EventManagerTestUtils.getEventProducerClass( childDelegationClassLoader)
				);
		int priceValue = 88;
		setPriceMethod.invoke( childEventProducer1Obj, priceValue);
		// ----------------------------------------------------------------------------------------
		
		// Verifying methods executed -------------------------------------------------------------
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				4, 
				eventTraceList.size()
				);
			int eventTraceListIndex = 0;
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName 
							+ ".staticListenerOfBothEvents"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".onPriceChange"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".staticListenerOfPriceChange"
							) 
					);
			Assert.assertTrue(
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( eventTraceListIndex++)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName 
							+ ".onPriceChange"
							) 
					);
			
			for( int eventTraceIndex = 0; eventTraceIndex < eventTraceList.size(); eventTraceIndex++) {
				Assert.assertEquals( 
						EventManagerTestUtils.getPriceChangeClass( childDelegationClassLoader),
						triggeredEventTraceList.get( eventTraceIndex)
						);
				Class<?> eventerInfoClass 
				= EventManagerTestUtils.getEventerInfoClass( childDelegationClassLoader);
				Object eventerInfo = eventerInfoClass.cast( eventerTraceList.get( eventTraceIndex));
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerObjOfEventerInfo( eventerInfo),
							childEventProducer1Obj
							);
					Assert.assertEquals( 
							EventManagerTestUtils.getEventerFieldOfEventerInfo( eventerInfo).getName(),
							"price"
							);
				Assert.assertEquals( 
						(Integer)eventResultTraceList.get( eventTraceIndex), 
						Integer.valueOf( priceValue)
						);
			} // for
		// ----------------------------------------------------------------------------------------
	}
	// --------------------------------------------------------------------------------------------
	
	// Test for field event from static field -----------------------------------------------------
	@Test
	public void testFieldEventFromStaticField() throws Throwable {
		// Instantiate EventRecipient object ------------------------------------------------------
		Object eventRecipientObj 
		= EventManagerTestUtils.eventRecipientFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientChild object -------------------------------------------------
		Object eventRecipientChildObj 
		= EventManagerTestUtils.eventRecipientChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		// Instantiate EventRecipientGrandChild object --------------------------------------------
		Object eventRecipientGrandChildObj 
		= EventManagerTestUtils.eventRecipientGrandChildFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		
		// Execute EventProducer.setPrice method -------------------------------------------------
		int priceValue = 88;
		EventManagerTestUtils.getSetPriceMethodOfEventProducerByStaticField( childDelegationClassLoader)
		.invoke( null, priceValue);
		// ----------------------------------------------------------------------------------------
		
		// Verifications --------------------------------------------------------------------------
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				4, 
				eventTraceList.size()
				);
			int eventTraceListIndex = -1;
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName
							+ ".staticListenerOfBothEvents"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".onPriceChange"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".staticListenerOfPriceChange"
							) 
					);
			Assert.assertTrue(
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName
							+ ".onPriceChange"
							) 
					);
				
			for( int eventTraceIndex = 0; eventTraceIndex < eventTraceList.size(); eventTraceIndex++) {
				Assert.assertEquals( 
						EventManagerTestUtils.getPriceChangeClass( childDelegationClassLoader),
						triggeredEventTraceList.get( eventTraceIndex)
						);
				Class<?> eventerInfoClass 
				= EventManagerTestUtils.getEventerInfoClass( childDelegationClassLoader);
				Object eventerInfo = eventerInfoClass.cast( eventerTraceList.get( eventTraceIndex));
					Assert.assertEquals( 
							null,
							EventManagerTestUtils.getEventerObjOfEventerInfo( eventerInfo)
							);
					Assert.assertEquals( 
							"price",
							EventManagerTestUtils.getEventerFieldOfEventerInfo( eventerInfo).getName()
							);
				Assert.assertEquals( 
						priceValue,
						eventResultTraceList.get( eventTraceIndex) 
						);
			} // for
				
		// ----------------------------------------------------------------------------------------
	}
	// --------------------------------------------------------------------------------------------
	
	// Test for static @OnEvent methods support ---------------------------------------------------
	@Ignore
	@Test
	public void ignore_testStaticListenerMethod() throws Throwable {
		/* Found that AspectJ's staticinitialization pointcut designator works at when object is 
		 * instantiated, and thereby it does not work for picking up static member (method or field) 
		 * of class of what object has not been instantiated.
		 * I cannot use advising java.lang.ClassLoader class because of variety of usage environment.
		 * Last resort may be getting necessary information at compile time: 
		 * 	http://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html 
		 */
		
		// Execute EventProducer.calculate method -------------------------------------------------
		Object eventProducerObj = EventManagerTestUtils.eventProducerFactory( childDelegationClassLoader);
		Method calculateMethod 
		= EventManagerTestUtils.getEventProducerCalculateMethod( eventProducerObj.getClass());
		calculateMethod.invoke( eventProducerObj, 4, 8);
		// ----------------------------------------------------------------------------------------
		
		// Verifying methods executed.
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				3, 
				eventTraceList.size()
				);
	}
	// --------------------------------------------------------------------------------------------
	
	// Test for withinThread attribute of @OnEvent annotation -------------------------------------
	protected static AtomicBoolean syncFlag = new AtomicBoolean( false);
	protected static class WithinThreadTestWorker implements Runnable {
		protected static long threadId;
		protected static boolean failureFlag = false;
		protected ClassLoader delegationClassLoader;
		public WithinThreadTestWorker( ClassLoader delegationClassLoader) {
			this.delegationClassLoader = delegationClassLoader;
		}
		
		@Override
		public void run() {
			threadId = Thread.currentThread().getId();
			
			synchronized( syncFlag) {
				try {
					// Instantiate EventRecipientGrandChild object --------------------------------
					Object eventRecipientGrandChildObj 
					= EventManagerTestUtils.eventRecipientGrandChildFactory( delegationClassLoader);
					// ----------------------------------------------------------------------------
					
					// Execute EventProducer.setPrice method --------------------------------------
					int priceValue = 88;
					EventManagerTestUtils
					.getSetPriceMethodOfEventProducerByStaticField( delegationClassLoader)
					.invoke( null, priceValue);
					// ----------------------------------------------------------------------------
				}
				catch( Throwable throwable) {
					failureFlag = true;
					if ( logger.isErrorEnabled()) {
						logger.error( 
								"Failure in executing EventProducer.setPrice method in woker thread.", 
								throwable
								);
					}
				}
				finally {
					syncFlag.set( true);
					syncFlag.notifyAll();
				}
			}
		} // public void run()
	} // protected static class WithinThreadTestWorker implements Runnable
	
	@Test
	public void testOnWithinThreadOfOnEventMethod() throws Throwable {
		// Instantiate EventRecipient object ------------------------------------------------------
		Object eventRecipientObj 
		= EventManagerTestUtils.eventRecipientFactory( childDelegationClassLoader);
		// ----------------------------------------------------------------------------------------
		
		Thread thread = new Thread( new WithinThreadTestWorker( childDelegationClassLoader));
		synchronized( syncFlag) {
			thread.start();
			while( !syncFlag.get()) {
				try {
					syncFlag.wait();
				}
				catch( InterruptedException exception) {
					if ( logger.isDebugEnabled()) {
						logger.debug( "Caught InterruptedException on syncFlag", exception);
					}
					Thread.currentThread().interrupt();
				}
			}
			syncFlag.notifyAll();
		} // synchronized
		
		Assert.assertTrue( !WithinThreadTestWorker.failureFlag);
		Assert.assertTrue( Thread.currentThread().getId() != WithinThreadTestWorker.threadId);
		
		Assert.assertEquals( 
				EventManagerTestUtils.getEventTraceListDump( eventTraceList) + "\n", 
				3, 
				eventTraceList.size()
				);
			int eventTraceListIndex = -1;
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName
							+ ".staticListenerOfBothEvents"
							) 
					);
			Assert.assertTrue( 
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientClassName
							+ ".staticListenerOfPriceChange"
							) 
					);
			Assert.assertTrue(
					EventManagerTestUtils.getEventTraceListDump( eventTraceList), 
					eventTraceList.get( ++eventTraceListIndex)
					.contains( 
							EventManagerTestUtils.EventRecipientGrandChildClassName
							+ ".onPriceChange"
							) 
					);
	}
	// --------------------------------------------------------------------------------------------
}
